#include <stdio.h>
#include <stdlib.h>
#include <direct.h>


#include "rterra.h"


#include "../tdp/trender/tdp_hmsd.hpp"
#include "../tdp/trender/tdp_normalutils.hpp"
#include "../tdp/trender/tdp_image.hpp"
#include "../tdp/tdp_cfile_filesystem.hpp"
#include "../tdp/tdp_vector.hpp"
#include "../tdp/tdp_string.hpp"
#include "../tdp/tdp_string.hpp"
#include "../tdp/tdp_checksum.hpp"
#include "../tdp/tdp_winding.hpp"

#include "../code/game/q_shared.h"
#include "../code/qcommon/qfiles.h"

extern "C"
{
#include "../q3map/progress.h"
#include "../tdp/tdp_toolsio.h"
#include "../common/polylib.h"
}


// Get rid of this macro
#undef DotProduct

using namespace TDP;

int planeScalar = 8;
terReal verticalScalar = 1020;
int lightChop = 1;
int chunkSize = 32;
int caulkChop = 32;
int hintChop = 0;
terReal hintMin = 40;
terVec3 mmOrigin(0, 0, 0);
int normalSmoothing = 1;
terReal lodScale = 1.0f;


#define PTOW(n) (((n) - dimension/2) * (terReal)planeScalar)

void ProcessHeightMap(const float *rawData, terVec3 *points, terVec2 *st, int dimension)
{
	int downDimension, rx, ry;
	float stScalar = (float)(dimension-1) / (float)(dimension);
	float stOffset = (0.5f / (float)dimension);

	downDimension = dimension - 1;

	for(int y=0;y<dimension;y++)
	{
		if(y == dimension - 1)
			ry = 0;
		else
			ry = dimension - 2 - y;

		for(int x=0;x<dimension;x++)
		{
			if(x == dimension - 1)
				rx = dimension - 2;
			else
				rx = x;

			st[0][0] = (terReal)x / ((terReal)dimension - 1) * stScalar + stOffset;
			st[0][1] = (terReal)y / ((terReal)dimension - 1) * stScalar + stOffset;

			points[0][0] = PTOW(x);
			points[0][1] = PTOW(y);
			points[0][2] = (rawData[rx + ry*downDimension] - 0.5) * verticalScalar;
			points++;
			st++;
		}
	}
}

struct rterraLODScalar : public terHMSDLODScaler
{
	virtual terReal LODScale(const terVec3 &point) const
	{
		return lodScale / planeScalar;
	}
};

struct rterraTriCounter : public terHMSDTriEmitter
{
	unsigned int numTris;

	rterraTriCounter()
	{
		numTris = 0;
	}

	virtual void EmitTri(int i1, int i2, int i3)
	{
		numTris++;
	}
};

struct rterraTriStorer : public terHMSDTriEmitter
{
	unsigned int *_result;

	rterraTriStorer(unsigned int *out)
	{
		_result = out;
	}

	virtual void EmitTri(int i1, int i2, int i3)
	{
		_result[0] = i1;
		_result[1] = i3;
		_result[2] = i2;
		_result += 3;
	}
};

/*
int LittleLong(int l)
{
	union
	{
		int l;
		unsigned char bytes[4];
	} u;

	u.bytes[0] = (unsigned char)(l >> 24);
	u.bytes[1] = (unsigned char)(l >> 16);
	u.bytes[2] = (unsigned char)(l >> 8);
	u.bytes[3] = (unsigned char)(l);

	return u.l;
}

short LittleShort(short s)
{
	union
	{
		int s;
		unsigned char bytes[2];
	} u;

	u.bytes[0] = (unsigned char)(s >> 8);
	u.bytes[1] = (unsigned char)(s);

	return u.s;
}

float LittleFloat(float f)
{
	union
	{
		int l;
		float f;
	} u;

	u.f = f;
	l = LittleLong(f);
	return u.f;
}
*/


// From mathlib.c
void NormalToLatLong( const terVec3 &normal, unsigned char bytes[2] ) {
	// check for singularities
	if ( normal[0] == 0 && normal[1] == 0 ) {
		if ( normal[2] > 0 ) {
			bytes[0] = 0;
			bytes[1] = 0;		// lat = 0, long = 0
		} else {
			bytes[0] = 128;
			bytes[1] = 0;		// lat = 0, long = 128
		}
	} else {
		int	a, b;

		// * 180 / pi * 255 / 360
		a = atan2( normal[1], normal[0] ) * 40.584510488433310621065359659991;
		a &= 0xff;

		b = acos( normal[2] ) * 40.584510488433310621065359659991;
		b &= 0xff;

		bytes[0] = b;	// longitude
		bytes[1] = a;	// lattitude
	}
}

bool TriInBounds(const unsigned int *tri, int length, int xmin, int xmax, int ymin, int ymax)
{
	int x, y;

	for(int i=0;i<3;i++)
	{
		y = tri[0] / length;
		x = tri[0] - (y * length);

		// Determine segmentation from the first index
		if(i == 0 && (x < xmin || x >= xmax || y < ymin || y >= ymax))
			return false;
		return true;
	}

	// None of the verts are drawable
	return false;
}

// Returns a bitmask of the textures used by this triangle
int TriMetaFlags(const unsigned int *tri, const terImage *detailmap, const terVec2 *tc)
{
	terVec2 mins, maxs;
	int minx, maxx, miny, maxy;
	int numChannels = 4;
	int metaFlags = 0;

	mins = tc[tri[0]];
	maxs = tc[tri[0]];

	// Find the texture coordinate boundaries
	for(int i=1;i<3;i++)
	{
		terVec2 coord = tc[tri[i]];

		for(int j=0;j<2;j++)
		{
			if(coord[j] < mins[j]) mins[j] = coord[j];
			if(coord[j] > maxs[j]) maxs[j] = coord[j];
		}
	}



	minx = (int)floor(mins[0] * (float)detailmap->Width() - 0.5f);
	miny = (int)floor(mins[1] * (float)detailmap->Height() - 0.5f);
	maxx = (int)ceil(maxs[0] * (float)detailmap->Width() - 0.5f);
	maxy = (int)ceil(maxs[1] * (float)detailmap->Height() - 0.5f);

	// Handle roundoff
	if(maxx > detailmap->Width() - 1) maxx = detailmap->Width() - 1;
	if(maxy > detailmap->Height() - 1) maxx = detailmap->Height() - 1;
	if(minx < 0) minx = 0;
	if(miny < 0) miny = 0;

	unsigned int stride = detailmap->Width();

	terReal floatWidth = (terReal)detailmap->Width();
	terReal floatHeight = (terReal)detailmap->Height();

	// Find out what layers are used by this texel
	for(unsigned int x=minx;x<maxx-1;x++)
	{
		for(unsigned int y=miny;y<maxy-1;y++)
		{
			const unsigned char *pixels[4];
			pixels[0] = detailmap->Pixels() + (x + y*stride)*4;
			pixels[1] = detailmap->Pixels() + (x+1 + y*stride)*4;
			pixels[2] = detailmap->Pixels() + (x + (y+1)*stride)*4;
			pixels[3] = detailmap->Pixels() + (x+1 + (y+1)*stride)*4;

			int pixelMetaFlags = 0;

			for(int i=0;i<4;i++)
				for(int j=0;j<4;j++)
					if(pixels[i][j])
						pixelMetaFlags |= 1 << j;

			if((metaFlags & pixelMetaFlags) == pixelMetaFlags) continue;	// Nothing new

			// Generate a winding for this interpolation area
			terVec2 tcWindingPoints[4];
			tcWindingPoints[0] = terVec2(0.0f, 0.0f);
			tcWindingPoints[1] = terVec2(1.0f, 0.0f);
			tcWindingPoints[2] = terVec2(1.0f, 1.0f);
			tcWindingPoints[3] = terVec2(0.0f, 1.0f);

			for(int i=0;i<3;i++)
			{
				tcWindingPoints[i][0] = (x + tcWindingPoints[i][0] + 0.5f) / floatWidth;
				tcWindingPoints[i][1] = (y + tcWindingPoints[i][1] + 0.5f) / floatHeight;
			}

			terWinding<terVec2> *winding = new terWinding<terVec2>(tcWindingPoints, 4);

			// Clip to the triangle
			bool culled = false;
			for(int i=0;i<3;i++)
			{
				terVec2 baseNormal;
				terVec2 normal;

				const terVec2 &p1 = tc[tri[i]];
				const terVec2 &p2 = tc[tri[(i+1)%3]];

				baseNormal = (p2 - p1).Normalize2();

				// Create perpendicular normal
				normal[0] = baseNormal[1];
				normal[1] = -baseNormal[0];

				terWinding<terVec2> *newWinding = winding->RemoveBehindPlane(normal, normal.DotProduct(p1));
				delete winding;
				winding = newWinding;
				if(!winding->NumPoints())
				{
					culled = true;
					break;
				}
			}
			delete winding;

			if(!culled)
				metaFlags |= pixelMetaFlags;
		}
	}

	return metaFlags;
}


// ExportMD3Segment: Returns true if a segment was exported
bool ExportMD3Segment(FILE *f, const terString &filename, int xmin, int xmax, int ymin, int ymax,
					  int dimension, const terVec2 *tc, const terVec3 *normals, const terVec3 *points,
					  unsigned int *tris, unsigned int numTris, const int *triMetaFlags, int metaFlags)
{
	terVector<unsigned char> vertUsed;
	terVector<bool> triUsed;
	terVector<unsigned int> itofVertRemaps;
	terVector<unsigned int> ftoiVertRemaps;
	unsigned int i, j, k, numRealTris, numRealVerts;
	terVec3 mins, maxs;
	unsigned int *tri;

	terString str;

	vertUsed.Alloc(dimension*dimension, 0);
	itofVertRemaps.Alloc(dimension*dimension);
	ftoiVertRemaps.Alloc(dimension*dimension);

	triUsed.Alloc(numTris, false);

	// Find all verts and tris relevent to this segment
	numRealTris = numRealVerts = 0;
	for(i=0;i<numTris;i++)
	{
		tri = tris + i*3;
		if(triMetaFlags[i] == metaFlags && TriInBounds(tri, dimension, xmin, xmax, ymin, ymax))
		{
			triUsed[i] = true;
			// Activate relevent verts
			for(j=0;j<3;j++)
				vertUsed[tri[j]] = 1;

			numRealTris++;
		}
	}

	// Create remaps for used verts and calculate bounds
	for(i=0;i<dimension*dimension;i++)
	{
		if(vertUsed[i])
		{
			if(!numRealVerts)
			{
				mins = maxs = points[i];
			}
			else
			{
				for(j=0;j<3;j++)
				{
					if(points[i][j] < mins[j]) mins[j] = points[i][j];
					if(points[i][j] > maxs[j]) maxs[j] = points[i][j];
				}
			}
			itofVertRemaps[i] = numRealVerts++;
		}
	}

	// Were any tris generated from this cluster?
	if(!numRealTris)
		return false;

	size_t surfBase = ftell(f);

	md3HighSurface_t sHeader;

	sHeader.lowSurface.ident = LittleLong(MD3_IDENT);
	strcpy(sHeader.lowSurface.name, "terrain");
	sHeader.lowSurface.flags = 0;
	sHeader.lowSurface.numFrames = LittleLong(1);
	sHeader.lowSurface.numShaders = LittleLong(1);
	sHeader.lowSurface.numVerts = LittleLong(numRealVerts);
	sHeader.lowSurface.numTriangles = LittleLong(numRealTris);
	sHeader.lightmapNum = 0;

	// Write preliminary surface header
	fwrite(&sHeader, sizeof(sHeader), 1, f);

	sHeader.lowSurface.ofsTriangles = LittleLong(ftell(f) - surfBase);

	// Write tris
	for(i=0;i<numTris;i++)
	{
		tri = tris + i*3;
		if(triUsed[i])
		{
			md3Triangle_t mTri;
			for(j=0;j<3;j++)
			{
				mTri.indexes[j] = LittleLong(itofVertRemaps[tri[j]]);
			}
			fwrite(&mTri, sizeof(mTri), 1, f);
		}
	}

	sHeader.lowSurface.ofsShaders = LittleLong(ftell(f) - surfBase);

	// Write shader info
	md3Shader_t mShader;

	str = filename;

	if(metaFlags != 15)
	{
		str = str + "$";
		if(!(metaFlags & 1)) str = str + "1";
		if(!(metaFlags & 2)) str = str + "2";
		if(!(metaFlags & 4)) str = str + "3";
		if(!(metaFlags & 8)) str = str + "4";
	}

	str = str + ".tga";
	strcpy(mShader.name, str.CStr());
	mShader.shaderIndex = 0;

	fwrite(&mShader, sizeof(mShader), 1, f);

	sHeader.ofsHighSt = LittleLong(ftell(f) - surfBase);
	
	// Write texcoords
	for(i=0;i<dimension*dimension;i++)
	{
		if(vertUsed[i])
		{
			md3HighSt_t mST;
			mST.st[0] = LittleFloat(tc[i][0]);
			mST.st[1] = LittleFloat(tc[i][1]);
			mST.lightmapST[0] = LittleFloat(tc[i][0]);
			mST.lightmapST[1] = LittleFloat(tc[i][1]);
			
			// Create tangent/binormal
			unsigned char latLongBytes[2];
			
			terVec3 tvecs[2] = { terVec3(1.0f, 0.0f, 0.0f), terVec3(0.0f, 1.0f, 0.0f) };
			for(int j=0;j<2;j++)
				tvecs[j] = (tvecs[j] - normals[i] * tvecs[j].DotProduct(normals[i])).Normalize2();

			NormalToLatLong(tvecs[0], latLongBytes);
			mST.svec = LittleShort( (latLongBytes[1] << 8) | latLongBytes[0] );
			NormalToLatLong(tvecs[1], latLongBytes);
			mST.tvec = LittleShort( (latLongBytes[1] << 8) | latLongBytes[0] );

			fwrite(&mST, sizeof(mST), 1, f);
		}
	}

	sHeader.lowSurface.ofsSt = LittleLong(ftell(f) - surfBase);

	// Write old version texcoords
	for(i=0;i<dimension*dimension;i++)
	{
		if(vertUsed[i])
		{
			md3St_t mST;
			mST.st[0] = LittleFloat(tc[i][0]);
			mST.st[1] = LittleFloat(tc[i][1]);
			fwrite(&mST, sizeof(mST), 1, f);
		}
	}
	
	sHeader.ofsHighXyzNormals = LittleLong(ftell(f) - surfBase);
	sHeader.lowSurface.ofsXyzNormals = LittleLong(ftell(f) - surfBase);

	// Write vertex info
	for(i=0;i<dimension*dimension;i++)
	{
		if(vertUsed[i])
		{
			md3HighXyzNormal_t mXYZN;
			unsigned char latlongBytes[2];

			mXYZN.xyz[0] = LittleFloat(points[i][0]);
			mXYZN.xyz[1] = LittleFloat(points[i][1]);
			mXYZN.xyz[2] = LittleFloat(points[i][2]);

			NormalToLatLong(normals[i], latlongBytes);
			mXYZN.normal = LittleShort( (latlongBytes[1] << 8) | latlongBytes[0] );

			fwrite(&mXYZN, sizeof(mXYZN), 1, f);
		}
	}

	sHeader.lowSurface.ofsEnd = LittleLong(ftell(f) - surfBase);
	
	size_t returnLocation = ftell(f);
	fseek(f, surfBase, SEEK_SET);
	fwrite(&sHeader, sizeof(sHeader), 1, f);
	fseek(f, returnLocation, SEEK_SET);

	return true;
}


void FillLine(int startX, int startY, terVec3 startPoint,
			  int endX, int endY, terVec3 endPoint,
			  unsigned char *pointOccupied, terVec3 *resultPoints, int dimension)
{
	int px, py, i, n;

	if(endY < startY)
		py = -1;
	else if(endY > startY)
		py = 1;
	else
		return;	// Horizontal scans will get taken care of by the scans

	n = (endY - startY) * py;

	if(endX < startX)
		px = -1;
	else if(endX > startX)
		px = 1;
	else
		px = 0;

	terPrintf("Scanning line from %i,%i to %i,%i\n", startX, startY, endX, endY);
	terPrintf("%f %f %f   %f %f %f\n", startPoint[0], startPoint[1], startPoint[2], endPoint[0], endPoint[1], endPoint[2]);
	// Scan the line
	for(i=0;i<=n;i++)
	{
		int x = startX + i * px;
		int y = startY + i * py;
		int idx = x + y * dimension;
		terReal t = (terReal)i / (terReal)n;

		pointOccupied[idx] = 1;
		resultPoints[idx] = startPoint + (endPoint - startPoint) * t;

		terPrintf("%i / %i [%i,%i]: %f %f %f\n", i, n, x, y, resultPoints[idx][0], resultPoints[idx][1], resultPoints[idx][2]);
	}
}

void SmoothNormals(terVector<terVec3> &normals, int dimension)
{
	terVector<terVec3> newNormals;

	newNormals.Alloc(dimension*dimension);

	for(int y=0;y<dimension-1;y++)
	{
		for(int x=0;x<dimension-1;x++)
		{
			int idx = x + y * dimension;
			terVec3 accum;

			accum = terVec3(0, 0, 0);//normals[idx];
			if(x > 0) accum += normals[idx-1];
			if(x < dimension - 2) accum += normals[idx+1];
			if(y > 0) accum += normals[idx-dimension];
			if(y < dimension - 2) accum += normals[idx+dimension];

			newNormals[idx] = accum.Normalize();
		}
	}

	newNormals.MoveTo(&normals);
}

void ScanPointLine(terVec3 *resultPoints, int dimension, terVec3 triPoints[3], int x[3], int y[3], unsigned char *pointHot)
{
	// Find the real extents
	int minCoords[2];
	int maxCoords[2];

	minCoords[0] = minCoords[1] = dimension + 1;
	maxCoords[0] = maxCoords[1] = -1;

	for(int i=0;i<3;i++)
	{
		if(minCoords[0] > x[i])
			minCoords[0] = x[i];
		if(minCoords[1] > y[i])
			minCoords[1] = y[i];

		if(maxCoords[0] <= x[i])
			maxCoords[0] = x[i]+1;
		if(maxCoords[1] <= y[i])
			maxCoords[1] = y[i]+1;
	}

	for(int i=0;i<2;i++)
	{
		if(maxCoords[i] >= dimension)
			maxCoords[i] = dimension-1;
		if(minCoords[i] < 0)
			minCoords[i] = 0;
	}

	terVec3 planeNormal = (triPoints[0] - triPoints[1]).Cross(triPoints[2] - triPoints[1]).Normalize();;
	terReal planeDist = planeNormal.DotProduct(triPoints[0]);
	terVec3 edgePlanes[3];
	terReal edgePlaneDists[3];
	for(int i=0;i<3;i++)
	{
		edgePlanes[i] = (triPoints[i] - triPoints[(i+1)%3]).Cross(terVec3(0, 0, 1));
		edgePlaneDists[i] = edgePlanes[i].DotProduct(triPoints[i]);
	}

	// Don't bother if this is pointing the wrong way
	if(planeNormal[2] < 0.0000001)
		return;

	// Check all points
	for(int py=minCoords[1];py<=maxCoords[1];py++)
	{
		for(int px=minCoords[0];px<=maxCoords[0];px++)
		{
			bool inBounds = true;
			int idx = py*dimension+px;
			terVec3 &basePoint = resultPoints[idx];
			for(int i=0;i<3;i++)
			{
				if(basePoint.DotProduct(edgePlanes[i]) - edgePlaneDists[i] >= 0.000001)
					inBounds = false;
			}
			if(!inBounds)
				continue;

			// Snap Z to the plane
			terReal newZ = (planeDist - basePoint[0]*planeNormal[0] - basePoint[1]*planeNormal[1]) / planeNormal[2];

			if(pointHot[idx/8] & (1 << (idx & 7)))
			{
				// Only assign this if it's greater than the existing one
				if(newZ > basePoint[2])
					basePoint[2] = newZ;
			}
			else
			{
				// This is the first Z value assigned to this, so force it
				basePoint[2] = newZ;
				pointHot[idx/8] |= (1 << (idx & 7));
			}
		}
	}
}

void ExportLightInfo(FILE *f, const terVec3 *points, const unsigned int *tris,
					 int dimension, int numTris, int superSample)
{
	terVector<unsigned char> pointOccupied;
	terVector<terVec3> resultPoints;
	terVector<terVec3> resultNormals;

	if(superSample != 1)
	{
		int newD = (dimension-1)*superSample+1;
		int x, y, subX, subY, i;
		terVector<terVec3> ssPoints;
		terVector<unsigned int> ssTris;

		ssPoints.Alloc(newD*newD);
		ssTris.Alloc(numTris*3);

		// Expand the points list
		for(x=0;x<dimension;x++)
			for(y=0;y<dimension;y++)
				ssPoints[x*superSample + (y*superSample)*newD] = points[x+y*dimension];

		// Interpolate intermediate values
		for(x=0;x<newD-1;x+=superSample)
		{
			for(y=0;y<newD-1;y+=superSample)
			{
				terVec3 cornerPoints[4];
				cornerPoints[0] = ssPoints[x+y*newD];
				cornerPoints[1] = ssPoints[x+superSample+y*newD];
				cornerPoints[2] = ssPoints[x+(y+superSample)*newD];
				cornerPoints[3] = ssPoints[x+superSample+(y+superSample)*newD];

				for(subY=0;subY<superSample;subY++)
				{
					terReal yt = (terReal)subY / (terReal)superSample;

					for(subX=0;subX<superSample;subX++)
					{
						terVec3 edgePoints[2];
						terReal xt = (terReal)subX / (terReal)superSample;
						
						edgePoints[0] = cornerPoints[0]*(1.0-xt) + cornerPoints[1]*xt;
						edgePoints[1] = cornerPoints[2]*(1.0-xt) + cornerPoints[3]*xt;

						ssPoints[x+subX + (y+subY)*newD] = edgePoints[0]*(1.0-yt) + edgePoints[1]*yt;
						//ssPoints[x+subX + (y+subY)*newD] = ssPoints[x + y*newD];
					}
				}
			}
		}

		// Expand tris list
		for(i=0;i<numTris*3;i++)
		{
			unsigned int idx = tris[i];

			y = idx / dimension;
			x = idx % dimension;

			ssTris[i] = x*superSample + y*superSample*newD;
		}

		ExportLightInfo(f, ssPoints, ssTris, newD, numTris, 1);

		return;
	}

	pointOccupied.Alloc(dimension*dimension, 0);
	resultPoints.Alloc(dimension*dimension);
	resultNormals.Alloc(dimension*dimension);

	// Set defaults
	for(int i=0;i<dimension*dimension;i++)
		resultPoints[i] = points[i];

	// Create vertical interpolator points
	terVector<unsigned char> pointHot;

	pointHot.Alloc(dimension*dimension + 1, 0);

	for(int i=0;i<numTris;i++)
	{
		int x[3];
		int y[3];
		terVec3 triPoints[3];

		for(int j=0;j<3;j++)
		{
			y[j] = tris[j] / dimension;
			x[j] = tris[j] - y[j] * dimension;

			triPoints[j] = points[tris[j]];
		}

		ScanPointLine(resultPoints, dimension, triPoints, x, y, pointHot);

		tris += 3;
	}

	// Calculate normals
	for(int y=0;y<dimension-1;y++)
	{
		for(int x=0;x<dimension-1;x++)
		{
			int idx = x + y * dimension;

			terVec3 nPoints[4];
			terVec3 normals[2];

			nPoints[0] = points[idx];
			nPoints[1] = points[idx+1];
			nPoints[2] = points[idx+dimension];
			nPoints[3] = points[idx+dimension+1];

			normals[0] = (nPoints[1] - nPoints[0]).Cross(nPoints[2] - nPoints[0]);
			normals[1] = (nPoints[2] - nPoints[3]).Cross(nPoints[1] - nPoints[3]);
			resultNormals[idx] = (normals[0] + normals[1]).Normalize();
		}
	}

	// Smooth normals
	for(int i=0;i<normalSmoothing;i++)
		SmoothNormals(resultNormals, dimension);

	// Calculate light points
	for(int y=lightChop/2;y<dimension-1;y+=lightChop)
	{
		for(int x=lightChop/2;x<dimension-1;x+=lightChop)
		{
			int idx = x + y * dimension;
			terVec3 svec, tvec, rp;
			terrainLightPoint_t lp;

			terVec3 resultNormal;

			resultNormal = resultNormals[idx];
			rp = resultPoints[idx] + terVec3(0, 0, 1);

			svec = terVec3(1, 0, 0);
			svec = (svec - (resultNormal * svec.DotProduct(resultNormal))).Normalize();

			tvec = terVec3(0, 1, 0);
			tvec = (tvec - (resultNormal * tvec.DotProduct(resultNormal))).Normalize();

			if(pointOccupied[idx])
				tvec = terVec3(1, 1, 1);
			else
				tvec = terVec3(1, 0, 0);

			for(int i=0;i<3;i++)
			{
				lp.position[i] = rp[i] + mmOrigin[i];
				lp.normal[i] = (unsigned char)(floorf(resultNormal[i] * 127.5 + 128));
			}
			fwrite(&lp, sizeof(lp), 1, f);
		}
	}
}


void ExportTriangularBrush(FILE *f, const terVec3 *points, terReal minZ, terReal maxZ, const char *shader)
{
	// Make sure this isn't perpendicular
	terVec3 normal = (points[0] - points[1]).Cross(points[2] - points[1]).Normalize();
	if(normal[2] < 0.000001)
		return;

	fputs("{\n", f);

	// Bottom edge
	fprintf(f, "( %f %f %f ) ( %f %f %f ) ( %f %f %f ) %s 0 0 0 0.25 0.25 0 0 0\n",
		points[1][0], points[1][1], minZ,
		points[0][0], points[0][1], minZ,
		points[2][0], points[2][1], minZ,
		shader);

	// Top edge
	fprintf(f, "( %f %f %f ) ( %f %f %f ) ( %f %f %f ) %s 0 0 0 0.25 0.25 0 0 0\n",
		points[0][0], points[0][1], points[0][2],
		points[1][0], points[1][1], points[1][2],
		points[2][0], points[2][1], points[2][2],
		shader);

	// 2-0 edge
	fprintf(f, "( %f %f %f ) ( %f %f %f ) ( %f %f %f ) %s 0 0 0 0.25 0.25 0 0 0\n",
		points[0][0], points[0][1], maxZ,
		points[2][0], points[2][1], maxZ,
		points[2][0], points[2][1], minZ,
		shader);

	// 1-2 edge
	fprintf(f, "( %f %f %f ) ( %f %f %f ) ( %f %f %f ) %s 0 0 0 0.25 0.25 0 0 0\n",
		points[2][0], points[2][1], maxZ,
		points[1][0], points[1][1], maxZ,
		points[1][0], points[1][1], minZ,
		shader);

	// 0-1 edge
	fprintf(f, "( %f %f %f ) ( %f %f %f ) ( %f %f %f ) %s 0 0 0 0.25 0.25 0 0 0\n",
		points[1][0], points[1][1], maxZ,
		points[0][0], points[0][1], maxZ,
		points[1][0], points[1][1], minZ,
		shader);

	fputs("}\n", f);
}


void ExportBoxBrush(FILE *f, const terVec3 &mins, const terVec3 &maxs, const char *shader)
{
	fputs("{\n", f);

	fprintf(f, "( %f %f %f ) ( %f %f %f ) ( %f %f %f ) %s 0 0 0 0.25 0.25 0 0 0\n",
		maxs[0], maxs[1], mins[2],
		mins[0], maxs[1], mins[2],
		mins[0], mins[1], mins[2],
		shader);

	fprintf(f, "( %f %f %f ) ( %f %f %f ) ( %f %f %f ) %s 0 0 0 0.25 0.25 0 0 0\n",
		mins[0], mins[1], maxs[2],
		mins[0], maxs[1], maxs[2],
		maxs[0], maxs[1], maxs[2],
		shader);

	fprintf(f, "( %f %f %f ) ( %f %f %f ) ( %f %f %f ) %s 0 0 0 0.25 0.25 0 0 0\n",
		mins[0], mins[1], maxs[2],
		maxs[0], mins[1], maxs[2],
		maxs[0], mins[1], mins[2],
		shader);

	fprintf(f, "( %f %f %f ) ( %f %f %f ) ( %f %f %f ) %s 0 0 0 0.25 0.25 0 0 0\n",
		maxs[0], mins[1], maxs[2],
		maxs[0], maxs[1], maxs[2],
		maxs[0], maxs[1], mins[2],
		shader);

	fprintf(f, "( %f %f %f ) ( %f %f %f ) ( %f %f %f ) %s 0 0 0 0.25 0.25 0 0 0\n",
		maxs[0], maxs[1], maxs[2],
		mins[0], maxs[1], maxs[2],
		mins[0], maxs[1], mins[2],
		shader);

	fprintf(f, "( %f %f %f ) ( %f %f %f ) ( %f %f %f ) %s 0 0 0 0.25 0.25 0 0 0\n",
		mins[0], maxs[1], maxs[2],
		mins[0], mins[1], maxs[2],
		mins[0], mins[1], mins[2],
		shader);

	fputs("}\n", f);
}

void ExportClipBrushes(FILE *f, const terVec3 *points, const unsigned int *tris, unsigned int numTris, const int *triAlphaFlags, terReal minZ)
{
	terVec3 localPoints[3];
	terReal maxZ;

	while(numTris)
	{
		if(*triAlphaFlags++)
		{
			localPoints[0] = points[tris[0]] + mmOrigin;
			localPoints[1] = points[tris[1]] + mmOrigin;
			localPoints[2] = points[tris[2]] + mmOrigin;

			maxZ = localPoints[0][2];
			if(localPoints[1][2] > maxZ) maxZ = localPoints[1][2];
			if(localPoints[2][2] > maxZ) maxZ = localPoints[2][2];

			ExportTriangularBrush(f, localPoints, minZ, maxZ, "common/full_clip");
		}

		numTris--;
		tris += 3;
	}
}

void ExportCaulk(FILE *fout, const terVec3 *points, const unsigned int *tris, unsigned int numTris, terReal minZ, int dimension)
{
	int x, y, subX, subY;

	for(y=0;y<dimension-1;y+=caulkChop)
	{
		for(x=0;x<dimension-1;x+=caulkChop)
		{
			terReal maxZ;
			terVec3 mins, maxs;
			terVec3 planes[4];
			winding_t *winding;

			mins[0] = PTOW(x);
			mins[1] = PTOW(y);
			maxs[0] = PTOW(x+caulkChop);
			maxs[1] = PTOW(y+caulkChop);

			float normals[12] = { 1, 0, 0, 0, 1, 0, -1, 0, 0, 0, -1, 0 };
			float dists[4] = { mins[0], mins[1], -maxs[0], -maxs[1] };

			maxZ = 999999999999.0f;

			for(unsigned int i=0;i<numTris;i++)
			{
				const unsigned int *tri = tris + i*3;

				winding = AllocWinding(3);
				winding->numpoints = 3;
				for(unsigned int j=0;j<3;j++)
				{
					const terVec3 &p = points[tri[j]];

					for(int k=0;k<3;k++)
						winding->p[j][k] = p[k];
				}

				// Try clipping this winding
				for(int j=0;j<4;j++)
				{
					ChopWindingInPlace(&winding, normals + j*3, dists[j], 0.0001);
					if(!winding)
						break;
				}

				if(winding)
				{
					for(int j=0;j<winding->numpoints;j++)
						if(winding->p[j][2] < maxZ)
							maxZ = winding->p[j][2];
					FreeWinding(winding);
				}
			}


			// Set up bounding box
			mins[2] = minZ;
			maxs[2] = maxZ - 1;		// Slip a little bit

			//ExportBoxBrush(fout, mins + mmOrigin, maxs + mmOrigin, "common/depthonly");
		}
	}
}

void ExportHints(FILE *fout, const terVec3 *points, int dimension)
{
	int x, y, subX, subY;
	terVec3 mins, maxs;

	for(y=0;y<dimension-1;y+=hintChop)
	{
		for(x=0;x<dimension-1;x+=hintChop)
		{
			terVec3 mins, maxs;
			mins = maxs = points[x + y * dimension];

			// Find bounds of this region
			for(subX=0;subX<=hintChop;subX++)
			{
				for(subY=0;subY<=hintChop;subY++)
				{
					terVec3 p = points[x + subX + (y + subY) * dimension];

					for(int i=0;i<3;i++)
					{
						if(p[i] < mins[i])
							mins[i] = p[i];
						if(p[i] > maxs[i])
							maxs[i] = p[i];
					}
				}
			}

			if(maxs[2] - mins[2] < hintMin)
				continue;

			ExportBoxBrush(fout, mins, maxs, "common/hint");
		}
	}
}


void usage()
{
	terPrintf("Usage: rterra -geometry <heightmap name> [options]\n");
	terPrintf("       rterra -merge <X> <Y> <Z> <map name> <heightmap name> [options]\n");
	exit(1);
}

struct cascadeCacheInfo
{
	unsigned long crc32;
	float manifoldWeight;
	float inversionWeight;
	float inversionTolerance;
	float inversionMinimum;
	float reorientationWeight;
};

int main(int argc, const char **argv)
{
	terVector<terVec3> points;
	terVector<terVec3> normals;
	terVector<terVec2> st;
	terVector<terReal> roughness;
	terVector<unsigned char> subdivideFlags;
	terVector<unsigned char> vertUsageFlags;
	terVector<unsigned int> vertRemaps;
	terVector<unsigned int> tris;
	terVector<float> rawData;
	terString heightmapName;
	terString mapName;
	terString fileName;
	terVec3 mins, maxs;
	terReal radius;
	int dimension;
	bool mergeMode = false;
	bool caulkMode = false;
	bool lightMode = false;
	bool geometryMode = false;
	bool genLODMode = false;
	int superSample = 1;

	if(argc < 2)
		usage();

	if(!strcmp(argv[1], "-genlod"))
	{
		if(argc < 3)
			usage();
		heightmapName = argv[2];
		genLODMode = true;
		argv += 3;
		argc -= 3;
	}
	else if(!strcmp(argv[1], "-geometry"))
	{
		if(argc < 3)
			usage();
		heightmapName = argv[2];
		geometryMode = true;
		argv += 3;
		argc -= 3;
	}
	else if(!strcmp(argv[1], "-collide"))
	{
		if(argc < 6)
			usage();
		mmOrigin = terVec3(atof(argv[2]), atof(argv[3]), atof(argv[4]));
		mapName = argv[5];
		heightmapName = argv[6];
		mergeMode = true;
		argv += 6;
		argc -= 6;
	}
	else if(!strcmp(argv[1], "-caulk"))
	{
		if(argc < 6)
			usage();
		mmOrigin = terVec3(atof(argv[2]), atof(argv[3]), atof(argv[4]));
		mapName = argv[5];
		heightmapName = argv[6];
		caulkMode = true;
		argv += 6;
		argc -= 6;
	}
	else if(!strcmp(argv[1], "-light"))
	{
		if(argc < 6)
			usage();
		mmOrigin = terVec3(atof(argv[2]), atof(argv[3]), atof(argv[4]));
		mapName = argv[5];
		heightmapName = argv[6];
		lightMode = true;
		argv += 6;
		argc -= 6;
	}
	else
		usage();

	while(argc)
	{
		if(!strcmp(argv[0], "-lod"))
		{
			argc--; argv++;
			if(!argc) usage();
			lodScale = atof(argv[0]);
			terPrintf("LOD scale is %f\n", lodScale);
			argc--; argv++;
		}
		else if(!strcmp(argv[0], "-vscale"))
		{
			argc--; argv++;
			if(!argc) usage();
			verticalScalar = atof(argv[0]);
			terPrintf("Vertical scale is %f\n", verticalScalar);
			argc--; argv++;
		}
		else if(!strcmp(argv[0], "-hscale"))
		{
			argc--; argv++;
			if(!argc) usage();
			planeScalar = atoi(argv[0]);
			terPrintf("Heightmap sample width is %i\n", planeScalar);
			argc--; argv++;
		}
		else if(!strcmp(argv[0], "-lightchop"))
		{
			argc--; argv++;
			if(!argc) usage();
			lightChop = atoi(argv[0]);
			terPrintf("Light subsampling is %i\n", lightChop);
			argc--; argv++;
		}
		else if(!strcmp(argv[0], "-super"))
		{
			argc--; argv++;
			if(!argc) usage();
			superSample = atoi(argv[0]);
			terPrintf("Light supersampling is %i\n", superSample);
			argc--; argv++;
		}
		else if(!strcmp(argv[0], "-smooth"))
		{
			argc--; argv++;
			if(!argc) usage();
			normalSmoothing = atoi(argv[0]);
			terPrintf("Normal smoothness is %i\n", normalSmoothing);
			argc--; argv++;
		}
		else if(!strcmp(argv[0], "-caulkchop"))
		{
			argc--; argv++;
			if(!argc) usage();
			caulkChop = atoi(argv[0]);
			terPrintf("Caulk chop boundary is %i\n", caulkChop);
			argc--; argv++;
		}
		else if(!strcmp(argv[0], "-hintchop"))
		{
			argc--; argv++;
			if(!argc) usage();
			hintChop = atoi(argv[0]);
			terPrintf("Hint chop boundary is %i\n", hintChop);
			argc--; argv++;
		}
		else if(!strcmp(argv[0], "-hintmin"))
		{
			argc--; argv++;
			if(!argc) usage();
			hintMin = atof(argv[0]);
			terPrintf("Minimum height difference for hint emission is %f\n", hintMin);
			argc--; argv++;
		}
		else if(!strcmp(argv[0], "-polychop"))
		{
			argc--; argv++;
			if(!argc) usage();
			chunkSize = atoi(argv[0]);
			terPrintf("Geometry segment size is %i\n", chunkSize);
			argc--; argv++;
		}
		else
		{
			argc--;
			argv++;
		}
	}

	// Set up filesystem
	char cwd[2048];
	_getcwd(cwd, sizeof(cwd));
	strcat(cwd, "\\");

	terCFSFileSystem fileSystem(cwd);

	fileName = heightmapName + ".r32";

	FILE *f = fopen(fileName.CStr(), "rb");
	if(!f)
	{
		terPrintf("%s failed to load", fileName.CStr());
		return 1;
	}

	// Load float data
	fseek(f, 0, SEEK_END);
	rawData.Alloc(ftell(f) / 4);
	fseek(f, 0, SEEK_SET);
	fread(rawData, 4, rawData.Count(), f);

	// Determine dimension
	dimension = 2;
	while(dimension < 16384)
	{
		if(dimension*dimension == rawData.Count())
			break;
		dimension *= 2;
	}
	if(dimension == 16384)
	{
		terPrintf("Terrain heightmap is too large, or is not a power of two");
		return 1;
	}
	dimension++;

	points.Alloc(dimension*dimension);
	normals.Alloc(dimension*dimension);
	st.Alloc(dimension*dimension);
	roughness.Alloc(dimension*dimension);
	subdivideFlags.Alloc(dimension*dimension, 0);

	ProcessHeightMap(rawData, points, st, dimension);
	rawData.Free();

	//terHMSDCalculateBaseRoughness(points, roughness, dimension);
	//terHMSDSubdivideMesh(points, roughness, subdivideFlags, dimension, rterraLODScalar());
	//terHMSDSplitMeshEdges(subdivideFlags, dimension);

	terLODReductionInfo redInfo;
	redInfo.manifoldWeight = 1.0f;
	redInfo.inversionWeight = -1;
	redInfo.inversionTolerance = 0.4;
	redInfo.inversionMinimum = 0.01;
	redInfo.reorientationWeight = 40.0f;


	{
		terVector<unsigned int> indexes;
		int trisToRemove;
		cascadeCacheInfo cciLocal;
		cascadeCacheInfo cciSaved;

		fileName = heightmapName + ".lod";

		// See if the cache already exists and is up to date
		if(genLODMode)
		{
			FILE *lodFile = fopen(fileName.CStr(), "rb");

			cciLocal.crc32 = terChecksumAdler32(0, points, sizeof(points));
			cciLocal.manifoldWeight = redInfo.manifoldWeight;
			cciLocal.inversionWeight = redInfo.inversionWeight;
			cciLocal.inversionTolerance = redInfo.inversionTolerance;
			cciLocal.inversionMinimum = redInfo.inversionMinimum;
			cciLocal.reorientationWeight = redInfo.reorientationWeight;

			if(lodFile && fread(&cciSaved, sizeof(cciSaved), 1, lodFile) == 1 && !memcmp(&cciSaved, &cciLocal, sizeof(cascadeCacheInfo)))
			{
				terPrintf("Terrain cascade for %s is up to date\n", fileName.CStr());
				return 0;
			}

			if(lodFile)
				fclose(lodFile);
			terPrintf("Terrain cascade for %s is out of date, rebuilding\n", fileName.CStr());
		}

		terLODInitialMeshForHeightfield(points, dimension, indexes);
		terPrintf("LOD: Generated %i indexes\n", indexes.Count());

		terPrintf("Bonding edges...\n");

		terLODInitMeshLOD(&redInfo, points, NULL, indexes, indexes.Count()/3, genLODMode);
		terPrintf("%i edges\n", redInfo.edges.Count());

		int edgeIndex;
		unsigned int scoreIndex;
		trisToRemove = redInfo.tris.Count() - ((dimension * dimension) * 0.04f * lodScale);

		int trisRemoved = 0;

		if(genLODMode)
		{
			FILE *lodFile = fopen(fileName.CStr(), "wb");
			memset(&cciSaved, 0, sizeof(cciSaved));
			fwrite(&cciSaved, sizeof(cciSaved), 1, lodFile);

			Progress_Init();
			Progress_SetLoad(trisToRemove);
			Progress_SetMessage("Rebuilding edge collapse cascade...");
			for(;;)
			{
				int numRemoved;
				if(!terLODCollapseEdge(&redInfo, points, NULL, &numRemoved, NULL, &edgeIndex, &scoreIndex))
					break;
				fwrite(&edgeIndex, sizeof(int), 1, lodFile);
				fwrite(&scoreIndex, sizeof(unsigned int), 1, lodFile);
				trisRemoved += numRemoved;
				Progress_SetProgress(trisRemoved);
			}

			fseek(lodFile, 0, SEEK_SET);
			fwrite(&cciLocal, sizeof(cciLocal), 1, lodFile);

			fclose(lodFile);
			return 0;
		}
		else
		{
			FILE *lodFile = fopen(fileName.CStr(), "rb");
			fread(&cciLocal, sizeof(cciLocal), 1, lodFile);

			redInfo.manifoldWeight = cciLocal.manifoldWeight;
			redInfo.inversionWeight = cciLocal.inversionWeight;
			redInfo.inversionTolerance = cciLocal.inversionTolerance;
			redInfo.inversionMinimum = cciLocal.inversionMinimum;
			redInfo.reorientationWeight = cciLocal.reorientationWeight;

			while(trisRemoved < trisToRemove)
			{
				int numRemoved;
				if(!fread(&edgeIndex, sizeof(int), 1, lodFile))
					break;
				if(!fread(&scoreIndex, sizeof(unsigned int), 1, lodFile))
					break;

				if(!terLODCollapseEdge(&redInfo, points, NULL, &numRemoved, NULL, &edgeIndex, &scoreIndex ))
					break;
				trisRemoved += numRemoved;
			}
			fclose(lodFile);
		}
	}

	redInfo.verts = points;

	rterraTriCounter counter;
	terLODEmitTris(&redInfo, counter);
	//terHMSDEmitTris(subdivideFlags, dimension, 1, counter);

	terPrintf("Generated %i triangles\n", counter.numTris);

	// Store result triangles the triangle results
	tris.Alloc(counter.numTris * 3);
	rterraTriStorer storer(tris);

	terLODEmitTris(&redInfo, storer);
	//terHMSDEmitTris(subdivideFlags, dimension, 1, storer);

	// Calculate channel meta flags for each tri
	terVector<int> triMetaFlags;
	terVector<int> triAlphaFlags;

	triMetaFlags.Alloc(tris.Count()/3);
	triAlphaFlags.Alloc(tris.Count()/3);

	// Calculate meta and alpha flags for each layer
	{
		terImage detailMapImage;
		terImage xMapImage;

		if(geometryMode)
		{
			terPrintf("Calculating detail meta flags...\n");
			terFileStream *fs = fileSystem.Open(heightmapName + "_d.png");

			detailMapImage.LoadPNG(fs);

			for(unsigned int i=0;i<triMetaFlags.Count();i++)
				triMetaFlags[i] = TriMetaFlags(tris + i*3, &detailMapImage, st);

			delete fs;
		}

		if(geometryMode || mergeMode)
		{
			terPrintf("Calculating alpha meta flags...\n");
			terFileStream *fs = fileSystem.Open(heightmapName + "_x.png");

			xMapImage.LoadPNG(fs);

			for(unsigned int i=0;i<triAlphaFlags.Count();i++)
			{
				triAlphaFlags[i] = (TriMetaFlags(tris + i*3, &xMapImage, st) & 7);
				if(triAlphaFlags[i] == 0)
					triMetaFlags[i] = -1;
			}

			delete fs;
		}
	}

	if(!geometryMode)
		triMetaFlags.Free();

	// Calculate bounds
	mins = maxs = points[0];
	radius = 0;
	for(int i=0;i<dimension*dimension;i++)
	{
		terVec3 p = points[i];
		terReal r = p.Length();

		if(r > radius) radius = r;

		for(int j=0;j<3;j++)
		{
			if(p[j] < mins[j]) mins[j] = p[j];
			if(p[j] > maxs[j]) maxs[j] = p[j];
		}
	}

	mins += mmOrigin;
	maxs += mmOrigin;

	if(lightMode)
	{
		terrainLightInfo_t tli;

		fileName = mapName + ".tlp";

		terPrintf("Exporting %s\n", fileName.CStr());

		FILE *fout = fopen(fileName.CStr(), "ab");

		tli.superSample = superSample;
		tli.dimension = (dimension - 1) * tli.superSample / lightChop;
		terPrintf("Supersampling: %i   Dimension: %i\n", superSample, tli.dimension);
		strcpy(tli.shaderName, heightmapName.CStr());
		if(!fwrite(&tli, sizeof(tli), 1, fout))
			terPrintf("WARNING: Write to TLP failed\n");
		// Export radiosity points
		ExportLightInfo(fout, points, tris, dimension, counter.numTris, 1);
		// Export terrain points
		ExportLightInfo(fout, points, tris, dimension, counter.numTris, superSample);

		fclose(fout);
	}

	if(mergeMode)
	{
		fileName = mapName + ".map";

		FILE *fout = fopen(fileName.CStr(), "a");
		fputs("// Collision generated by RTERRA\n", fout);

		fputs("{\n", fout);
		fputs("\"classname\" \"func_group\"\n", fout);

		ExportClipBrushes(fout, points, tris, counter.numTris, triAlphaFlags, mins[2] - 2);

		//if(caulkChop)
		//	ExportCaulk(fout, points, tris, tris.Count() / 3, mins[2] - 2, dimension);
		//if(hintChop)
		//	ExportHints(fout, points, dimension);

		fputs("}\n", fout);

		fclose(fout);
	}

	if(caulkMode)
	{
		fileName = mapName + ".map";

		FILE *fout = fopen(fileName.CStr(), "a");
		fputs("// Caulk generated by RTERRA\n", fout);

		fputs("{\n", fout);
		fputs("\"classname\" \"func_group\"\n", fout);

		ExportCaulk(fout, points, tris, tris.Count() / 3, mins[2] - 2, dimension);

		fputs("}\n", fout);

		fclose(fout);
	}

	// Generate normals
	terAccumulateNormalsAndSTVecs(points, st, tris, counter.numTris, normals, NULL, NULL);
	terFinalizeNormalsAndSTVecs(NULL, NULL, dimension*dimension, normals, NULL);


	if(geometryMode)
	{
		// Caulk needs to be at the same resolution as geometry
		terPrintf("Exporting geometry...\n");
		fileName = heightmapName + ".md3";

		FILE *fout = fopen(fileName.CStr(), "wb");

		md3Header_t mHeader;

		mHeader.ident = LittleLong(MD3_IDENT);
		mHeader.version = LittleLong(MD3_HIGH_VERSION);

		strcpy(mHeader.name, fileName.CStr());

		mHeader.flags = LittleLong(MD3_IS_LIGHTMAPPED);

		mHeader.numFrames = LittleLong(1);
		mHeader.numTags = LittleLong(0);
		mHeader.numSurfaces = 0;

		mHeader.numSkins = 0;

		mHeader.ofsFrames = 0;
		mHeader.ofsTags = 0;

		// Write initial header
		fwrite(&mHeader, sizeof(mHeader), 1, fout);

		mHeader.ofsSurfaces = LittleLong(ftell(fout));

		fileName = heightmapName;// + ".tga";
		for(int y=0;y<dimension;y+=chunkSize)
		{
			for(int x=0;x<dimension;x+=chunkSize)
			{
				for(int metaFlags=0;metaFlags<16;metaFlags++)
					if(ExportMD3Segment(fout, fileName.CStr(), x, x+chunkSize, y, y+chunkSize,
						dimension, st, normals, points, tris, tris.Count() / 3, triMetaFlags, metaFlags))
						mHeader.numSurfaces++;
			}
		}

		// Byte swap surface count
		mHeader.numSurfaces = LittleLong(mHeader.numSurfaces);

		mHeader.ofsFrames = LittleLong(ftell(fout));

		// Write the frame
		md3Frame_t mFrame;

		for(int j=0;j<3;j++)
		{
			mFrame.bounds[0][j] = LittleFloat(mins[j]);
			mFrame.bounds[1][j] = LittleFloat(maxs[j]);
			mFrame.localOrigin[j] = 0;
		}
		strcpy(mFrame.name, "reference");
		mFrame.radius = LittleFloat(radius);

		fwrite(&mFrame, sizeof(mFrame), 1, fout);

		mHeader.ofsEnd = LittleLong(ftell(fout));
		fseek(fout, 0, SEEK_SET);
		fwrite(&mHeader, sizeof(mHeader), 1, fout);
		fclose(fout);
	}
}
